﻿#region 참조 네임스페이스

using Skying.Windows.API;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

#endregion

namespace Skying.Resources
{
	/// <summary>
	/// 특정 모듈에 대한 리소스를 나타내는 추상 클래스를 정의한다.
	/// </summary>
	public abstract class Resource
	{
		#region 상수

		/// <summary>
		/// 리소스에 대한 모듈의 핸들 상수를 나타낸다.
		/// </summary>
		protected readonly IntPtr _MODULE_HANDLE;

		#endregion

		#region 변수

		/// <summary>
		/// 리소스 이름에 대한 핸들을 나타낸다.
		/// </summary>
		protected IntPtr _nameHandle;

		#endregion

		#region 프로퍼티

		/// <summary>
		/// 리소스의 유형에 대한 핸들을 가져온다.
		/// </summary>
		public IntPtr TypeHandle
		{
			get;
			private set;
		}

		/// <summary>
		/// 리소스의 유형 열거자를 가져온다.
		/// </summary>
		public ResourceTypes ResourceType
		{
			get;
			protected set;
		}

		/// <summary>
		/// 리소스의 이름을 가져온다.
		/// </summary>
		public string Name
		{
			get;
			private set;
		}

		/// <summary>
		/// 리소스의 별칭을 가져온다.
		/// </summary>
		public string Alias
		{
			get;
			private set;
		}

		/// <summary>
		/// 리소스 이름에 대한 핸들을 가져온다.
		/// </summary>
		public IntPtr NameHandle
		{
			get
			{
				return IsInt(this._nameHandle) ? this._nameHandle : Marshal.StringToHGlobalAuto(this.Name);
			}
		}

		#endregion

		#region 생성자/소멸자

		protected Resource(IntPtr module, IntPtr type, IntPtr name)
		{
			_MODULE_HANDLE = module;

			this.TypeHandle = type;

			this.ResourceType = ConvertType(type);

			this.Name = this.ResourceType.ToString();
			this._nameHandle = name;
			this.Alias = IsInt(name) ? name.ToString() : Marshal.PtrToStringAuto(name);

			this.CombineResource();
		}

		/// <summary>
		/// 리소스 타입과 이름을 통해 <see cref="Skying.Resources.Resource"/>의 인스턴스를 가져온다.
		/// </summary>
		/// <param name="moduleHandle">로드된 모듈의 핸들을 설정한다.</param>
		/// <param name="type">리소스의 타입을 설정한다.</param>
		/// <param name="name">리소스의 이름 핸들을 설정한다.</param>
		/// <returns><see cref="Skying.Resources.Resource"/>로부터 파생된 리소스 인스턴스가 반환된다.</returns>
		internal static Resource GetInstance(IntPtr moduleHandle, IntPtr type, IntPtr name)
		{
			Resource resource = null;

			ResourceTypes resourceType = Resource.ConvertType(type);

			switch (resourceType)
			{
				case ResourceTypes.RT_BITMAP:

					resource = new BitmapResource(moduleHandle, type, name);

					break;

				case ResourceTypes.RT_GROUP_ICON:

					resource = new IconGroupResource(moduleHandle, type, name);

					break;

				case ResourceTypes.RT_GROUP_CURSOR:

					resource = new CursorGroupResource(moduleHandle, type, name);

					break;

				case ResourceTypes.RT_STRING:

					resource = new StringResource(moduleHandle, type, name);

					break;
			}

			return resource;
		}

		#endregion

		#region 리소스

		/// <summary>
		/// 리소스를 구성한다.
		/// </summary>
		public abstract void CombineResource();

		/// <summary>
		/// 리소스 바이너리를 메모리에서 로드한다.
		/// </summary>
		/// <param name="type">리소스의 타입으로 <see cref="Skying.Windows.API.Kernel32.EnumResourceNames"/>의 콜백으로부터 전달된다.</param>
		/// <param name="name">리소스의 이름으로 <see cref="Skying.Windows.API.Kernel32.EnumResourceNames"/>의 콜백으로부터 전달된다.</param>
		/// <returns>리소스의 바이너리가 반환된다.</returns>
		protected byte[] LoadResourceBinary(IntPtr type, IntPtr name)
		{
			byte[] result = null;

			IntPtr hResourceInfo = Kernel32.FindResource(_MODULE_HANDLE, name, type);

			if (!hResourceInfo.Equals(IntPtr.Zero))
			{
				int size = Kernel32.SizeofResource(_MODULE_HANDLE, hResourceInfo);

				IntPtr hResource = Kernel32.LoadResource(_MODULE_HANDLE, hResourceInfo);

				if (!hResource.Equals(IntPtr.Zero))
				{
					result = new byte[size];

					IntPtr hLock = Kernel32.LockResource(hResource);

					if (!hLock.Equals(IntPtr.Zero))
					{
						Marshal.Copy(hLock, result, 0, size);
					}
				}
			}

			return result;
		}

		#endregion

		#region 도움 메서드

		/// <summary>
		/// <see cref="Skying.Windows.API.Kernel32.EnumResourceNames"/>으로부터 전달된 리소스의 타입을 <see cref="Skying.Resources.ResourceTypes"/>으로 변환한다.
		/// </summary>
		/// <param name="type">리소스의 타입으로 <see cref="Skying.Windows.API.Kernel32.EnumResourceNames"/>의 콜백으로부터 전달된다.</param>
		/// <returns>변환된 <see cref="Skying.Resources.ResourceTypes"/>이 반환된다.</returns>
		private static ResourceTypes ConvertType(IntPtr type)
		{
			ResourceTypes resourceType = (ResourceTypes)(IntPtr.Size == 4 ? type.ToInt32() : type.ToInt64());

			//if (type.size)
			//{
			switch (resourceType)
				{
					case ResourceTypes.RT_ACCELERATOR:
					case ResourceTypes.RT_ANICURSOR:
					case ResourceTypes.RT_ANIICON:
					case ResourceTypes.RT_BITMAP:
					case ResourceTypes.RT_CURSOR:
					case ResourceTypes.RT_DIALOG:
					case ResourceTypes.RT_DLGINCLUDE:
					case ResourceTypes.RT_FONT:
					case ResourceTypes.RT_FONTDIR:
					case ResourceTypes.RT_GROUP_CURSOR:
					case ResourceTypes.RT_GROUP_ICON:
					case ResourceTypes.RT_HTML:
					case ResourceTypes.RT_ICON:
					case ResourceTypes.RT_MANIFEST:
					case ResourceTypes.RT_MENU:
					case ResourceTypes.RT_MESSAGETABLE:
					case ResourceTypes.RT_PLUGPLAY:
					case ResourceTypes.RT_RCDATA:
					case ResourceTypes.RT_STRING:
					case ResourceTypes.RT_VERSION:
					case ResourceTypes.RT_VXD:

						return resourceType;

					default:

						return ResourceTypes.RT_UNKNOWN;
				}
			//}
			//else
			//{
			//	return ResourceTypes.RT_OTHER;
			//}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="value"></param>
		/// <returns></returns>
		private static bool IsInt(IntPtr value)
		{
			try
			{
				return (IntPtr.Size == 4 ? value.ToInt32() : value.ToInt64()) <= ushort.MaxValue;
			}
			catch
			{
				return false;
			}
		}

		#endregion

		#region System.Object 구현

		/// <summary>
		/// <see cref="Skying.Resources.Resource"/>의 인스턴스를 나타내는 <see cref="System.String"/>을 가져온다.
		/// </summary>
		/// <returns><see cref="Skying.Resources.Resource"/>의 인스턴스를 나타내는 <see cref="System.String"/>이 반환된다.</returns>
		public override string ToString()
		{
			return base.ToString();
		}

		#endregion
	}
}